import uuid, os
from server.codec import get_codec, SerializeJson
from server.rpc.client import RpcClient
from asyncio.queues import Queue
import asyncio
from asyncio import iscoroutine
from typing import Optional
import traceback
from fastapi import WebSocket, WebSocketDisconnect
from connmanage import connManage
import g
import abc


class _App(abc.ABC):
    def __init__(self, ws: WebSocket, *args, **kwargs):
        self.id = uuid.uuid1().__str__()
        self.ws = ws
        self.address = self.ws.base_url.netloc
        self.ip = self.ws.client.host
        self.create_task(self.on_open())

    @staticmethod
    def create_task(func, name=None):
        return asyncio.create_task(func, name=name)

    # 开始工作
    async def handle(self):
        await self.ws.accept()
        while True:
            try:
                msg = await self.ws.receive_text()
            except WebSocketDisconnect:
                # print("链接断开")
                break
            except Exception as e:
                print(f"gateway handle error[{e}]->{traceback.format_exc()}")
                break

            await self.on_message(msg)

        await self.on_close()

    # 收到消息
    @abc.abstractmethod
    async def on_message(self, msg):
        ...

    # 链接成功
    async def on_open(self):
        ...

    # 链接关闭
    async def on_close(self):
        ...

    # 发送消息
    async def send(self, msg):
        await self.ws.send_text(msg)


# 游戏逻辑处理
class GatewayApp(_App):
    serialize_type = SerializeJson
    codec = get_codec(serialize_type)

    def __init__(self, ws, *args, **kwargs):
        _App.__init__(self, ws, *args, **kwargs)

        self.uid = None  # 玩家uid，登录后设置
        self.worker_url = None  # login分配的worker
        self.write_msg_queue: Queue = Queue()  # 队列保证消息顺序发送
        asyncio.create_task(self._write())

    async def _write(self):
        while True:
            msg = await self.write_msg_queue.get()
            if msg == "break":
                break

            try:
                await self.ws.send_text(msg)
            except Exception as e:
                print(f"GatewayApp._write error=>{e}")

    def send(self, msg):
        self.write_msg_queue.put_nowait(msg)

    async def on_open(self):
        """链接成功"""
        await connManage.add_conn(self)
        self.send("websocket connect success!")

    async def on_close(self):
        """链接关闭"""
        self.uid = None
        self.worker_url = None
        await connManage.remove_conn(self)
        self.send("break")

        await super().on_close()

    async def on_message(self, msg):
        """收到消息"""
        await self.handle_message(msg)

    async def handle_error(self, err_msg):
        """错误处理"""
        self.response("", err_msg)

    async def handle_message(self, msg):
        """消息处理"""
        body = self.codec.decode(msg)
        # 解析失败,无效的数据
        if not body:
            return await self.handle_error("invalid data!")

        _req = self.codec.parse_body(body)
        if not isinstance(_req, dict) or "a" not in _req or "p" not in _req:
            return await self.handle_error("invalid body!")

        _a = _req["a"]
        _a2Handle = {
            "api": self.do_api  # 游戏逻辑api处理
        }
        _handle = _a2Handle.get(_a)
        if not _handle:
            return await self.handle_error(f"a:{_a} undefined!")

        do_handle = _handle(_req)
        if iscoroutine(do_handle):
            return await do_handle

        return do_handle

    async def do_api(self, req):
        """游戏逻辑api处理"""
        api_name, api_args, client_rid = req["p"], req["d"], req.get("rid")

        is_login = True if api_name == "login.login" else False
        if is_login and self.uid:
            return await self.handle_error("已登陆")

        worker = await self.get_worker(api_name, api_args)
        if not worker:
            # 服务器异常
            if self.uid or is_login:
                err_msg = "服务器开小差啦~~请稍后再试"
            # 掉线 or 未登陆
            else:
                err_msg = "请先登陆"

            return await self.handle_error(err_msg)

        req["ip"] = self.ip
        unique_key = self.uid if self.uid is not None else self.id  # 如果没有uid(登陆注册)传conn.ID
        worker.callback(
            "api",  # 服务方法名
            [unique_key, self.address, req],  # 服务方法参数
            self.response,
        )

    async def get_worker(self, api_name, api_args) -> Optional[RpcClient]:
        """获取一个可用的worker"""
        if api_name == "login.login":
            # 登陆分配一个
            self.worker_url = g.rpcManage.rand_worker_url()
            if not self.worker_url:
                return
        else:
            # 还未登陆
            if not self.worker_url or not self.uid:
                return

            # 服务不可用，重新分配一个
            if not g.rpcManage.is_ready(self.worker_url):
                self.worker_url = g.rpcManage.rand_worker_url()

        worker = g.rpcManage.get_worker(self.worker_url)

        return worker

    def pre_response(self, result):
        """返回响应前"""
        if not result:
            return

        api = result.get("api")
        # 登陆成功记录uid
        if api == "login.login":
            self.uid = result["uid"]
            connManage.set_conn_uid(self.uid, self)

    def response(self, result, err):
        """返回响应"""
        self.pre_response(result)
        res = self.codec.fmt_response(result, err)
        msg = self.codec.encode(res)
        self.send(msg)


# gm处理
class GmApp(_App):
    def __init__(self, ws, *args, **kwargs):
        _App.__init__(self, ws, *args, **kwargs)

    @staticmethod
    def stop_server():
        pid = os.getpid()
        if os.name == 'nt':
            os.system('taskkill /PID %s /T /F' % pid)
        else:
            os.kill(pid, 9)

        os._exit(0)

    async def on_message(self, msg):
        if msg == "stop_server":
            self.stop_server()
